src/views/product/ProductList.vue
<template>
  <div class="product-list-container">
    <div class="header">
      <h1>商品列表</h1>
      <el-input v-model="searchQuery" placeholder="搜索商品" prefix-icon="Search" class="search-input" />
    </div>
    
    <div class="filter-section">
      <el-select v-model="categoryId" placeholder="选择分类" class="filter-select">
        <el-option label="全部分类" value="" />
        <el-option v-for="category in categories" :key="category.id" :label="category.name" :value="category.id" />
      </el-select>
      
      <el-slider 
        v-model="priceRange" 
        range 
        :min="0" 
        :max="10000" 
        show-stops 
        :tooltip="{ formatter: (value: any) => `¥${value}` }" 
      />
      
      <el-select v-model="sortBy" placeholder="排序方式" class="filter-select">
        <el-option label="默认排序" value="" />
        <el-option label="价格从低到高" value="price_asc" />
        <el-option label="价格从高到低" value="price_desc" />
        <el-option label="最新上架" value="newest" />
      </el-select>
    </div>
    
    <div class="product-grid">
      <div v-for="product in products" :key="product.id" class="product-card">
        <img :src="product.images?.[0] || 'default-image.jpg'" alt="product.name" class="product-image" />
        <div class="product-info">
          <h3 class="product-name">{{ product.name }}</h3>
          <p class="product-price">¥{{ product.price }}</p>
          <p class="product-category">{{ product.category?.name }}</p>
          <div class="product-actions">
            <el-button type="primary" size="small" @click="addToCart(product.id)">加入购物车</el-button>
            <el-button size="small" @click="viewProduct(product.id)">查看详情</el-button>
          </div>
        </div>
      </div>
    </div>
    
    <div class="pagination">
      <el-pagination
        v-model:current-page="currentPage"
        v-model:page-size="pageSize"
        :page-sizes="[12, 24, 36]"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { productService } from '../../api/productService';
import { cartService } from '../../api/cartService';

const router = useRouter();
const products = ref<any[]>([]);
const categories = ref<any[]>([]);
const total = ref(0);
const currentPage = ref(1);
const pageSize = ref(12);
const searchQuery = ref('');
const categoryId = ref('');
const priceRange = ref([0, 10000]);
const sortBy = ref('');

const fetchProducts = async () => {
  try {
    const response = await productService.getProducts({
      page: currentPage.value,
      limit: pageSize.value,
      search: searchQuery.value,
      category: categoryId.value,
      minPrice: priceRange.value[0],
      maxPrice: priceRange.value[1],
      sortBy: sortBy.value
    });
    
    // 添加对响应数据的验证
    if (response && typeof response === 'object' && response.data) {
      products.value = response.data.products || [];
      total.value = response.data.pagination?.total || 0;
    } else {
      throw new Error('Invalid response data format');
    }
  } catch (error) {
    ElMessage.error('获取商品列表失败');
  }
};

const fetchCategories = async () => {
  try {
    const response = await productService.getCategories();
    
    // 添加对响应数据的验证
    if (response && typeof response === 'object' && response.data) {
      categories.value = response.data || [];
    } else {
      throw new Error('Invalid response data format');
    }
  } catch (error) {
    ElMessage.error('获取分类失败');
  }
};

const addToCart = async (productId: number) => {
  try {
    await cartService.addToCart({ productId, quantity: 1 });
    ElMessage.success('已成功加入购物车');
  } catch (error) {
    ElMessage.error('加入购物车失败，请先登录');
    router.push('/login');
  }
};

const viewProduct = (productId: number) => {
  router.push(`/product/${productId}`);
};

const handleSizeChange = (size: number) => {
  pageSize.value = size;
  fetchProducts();
};

const handleCurrentChange = (current: number) => {
  currentPage.value = current;
  fetchProducts();
};

// 监听搜索和筛选条件变化
const handleSearchAndFilter = () => {
  currentPage.value = 1;
  fetchProducts();
};

Object.assign(window, {
  handleSearchAndFilter
});

onMounted(() => {
  fetchProducts();
  fetchCategories();
});
</script>

<style scoped>
.product-list-container {
  padding: 20px;
  max-width: 1200px;
  margin: 0 auto;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.search-input {
  width: 300px;
}

.filter-section {
  display: flex;
  gap: 16px;
  align-items: center;
  margin-bottom: 20px;
  padding: 16px;
  background-color: #f8f9fa;
  border-radius: 8px;
}

.filter-select {
  width: 150px;
}

.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 20px;
  margin-bottom: 20px;
}

.product-card {
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  padding: 16px;
  transition: box-shadow 0.3s;
}

.product-card:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.product-image {
  width: 100%;
  height: 200px;
  object-fit: cover;
  border-radius: 4px;
  margin-bottom: 12px;
}

.product-name {
  font-size: 16px;
  margin-bottom: 8px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.product-price {
  font-size: 18px;
  color: #f56c6c;
  font-weight: bold;
  margin-bottom: 8px;
}

.product-category {
  font-size: 14px;
  color: #606266;
  margin-bottom: 12px;
}

.product-actions {
  display: flex;
  gap: 8px;
}

.pagination {
  display: flex;
  justify-content: center;
  margin-top: 20px;
}

/* 响应式设计 */
@media (max-width: 1200px) {
  .product-grid {
    grid-template-columns: repeat(4, 1fr);
    gap: 20px;
  }
  
  .filter-section {
    flex-direction: row;
    flex-wrap: wrap;
  }
  
  .filter-select {
    width: 30%;
    margin-bottom: 15px;
  }
}

@media (max-width: 992px) {
  .product-grid {
    grid-template-columns: repeat(3, 1fr);
  }
  
  .filter-select {
    width: 45%;
  }
}

@media (max-width: 768px) {
  .header {
    flex-direction: column;
    align-items: stretch;
    gap: 15px;
  }
  
  .search-input {
    width: 100%;
  }
  
  .filter-section {
    flex-direction: column;
  }
  
  .filter-select {
    width: 100%;
  }
  
  .product-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 15px;
  }
  
  .product-card {
    padding: 12px;
  }
  
  .product-image {
    height: 150px;
  }
  
  .product-name {
    font-size: 14px;
  }
  
  .product-price {
    font-size: 16px;
  }
  
  .product-actions {
    flex-direction: column;
    gap: 5px;
  }
  
  .product-actions .el-button {
    width: 100%;
  }
}

@media (max-width: 480px) {
  .product-list-container {
    padding: 10px;
  }
  
  .product-grid {
    grid-template-columns: 1fr;
  }
  
  .product-image {
    height: 200px;
  }
  
  .product-actions {
    flex-direction: row;
  }
  
  .product-actions .el-button {
    width: auto;
    flex: 1;
    font-size: 12px;
    padding: 8px 10px;
  }
}
</style>